package com.ccp.dev.system.aop;

import com.alibaba.druid.util.StringUtils;
import com.ccp.dev.core.basic.annotion.Action;
import com.ccp.dev.core.basic.annotion.ActionExecOrder;
import com.ccp.dev.core.basic.base.BaseService;
import com.ccp.dev.core.basic.consts.SystemConst;
import com.ccp.dev.core.basic.engine.FreemarkEngine;
import com.ccp.dev.core.basic.model.SysAuditModelType;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.util.*;
import com.ccp.dev.core.util.log.SysAuditThreadLocalHolder;
import com.ccp.dev.system.model.SysAudit;
import com.ccp.dev.system.model.SysLogSwitch;
import com.ccp.dev.system.model.SysRole;
import com.ccp.dev.system.model.SysUser;
import com.ccp.dev.system.service.SysAuditService;
import com.ccp.dev.system.service.SysLogSwitchService;
import com.ccp.dev.system.service.SysOrgService;
import com.ccp.dev.system.service.SysRoleService;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModelException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 操作日志切面
 *
 * @author zw
 * @date 2019-07-31 08:04:01
 */
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE-1)
@Component
public class LogAspect {

    private Log logger = LogFactory.getLog(LogAspect.class);

    @Pointcut("execution(* com.ccp..controller..*.*(..))")
    public void logPointCut() {}

    @Resource
    private SysLogSwitchService sysLogSwitchService;
    @Resource
    private SysOrgService sysOrgService;
    @Resource
    private FreemarkEngine freemarkEngine;
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysAuditService sysAuditService;

    @Value("${top.org.name}")
    private String topOrgName;

    private  static WorkQueue wq = new WorkQueue(10);

    private static boolean isCommonServicesInited = false;

    private static Map<String,Object> commonServices = new HashMap<String, Object>();
    /**
     * 添加FreeMarker可访问的类静态方法的字段
     */
    static Map<String, TemplateHashModel> STATIC_CLASSES = new HashMap<String, TemplateHashModel>();
    static{
        try {
            BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_2_3_28).build();
            TemplateHashModel staticModel = beansWrapper.getStaticModels();
            STATIC_CLASSES.put(Long.class.getSimpleName(), (TemplateHashModel) staticModel.get(java.lang.Long.class.getName()));
            STATIC_CLASSES.put(Integer.class.getSimpleName(), (TemplateHashModel) staticModel.get(java.lang.Integer.class.getName()));
            STATIC_CLASSES.put(java.lang.String.class.getSimpleName(), (TemplateHashModel) staticModel.get(java.lang.String.class.getName()));
            STATIC_CLASSES.put(Short.class.getSimpleName(), (TemplateHashModel) staticModel.get(java.lang.Short.class.getName()));
            STATIC_CLASSES.put(Boolean.class.getSimpleName(), (TemplateHashModel) staticModel.get(java.lang.Boolean.class.getName()));
            STATIC_CLASSES.put(StringUtil.class.getSimpleName(),(TemplateHashModel) staticModel.get(com.ccp.dev.core.util.StringUtil.class.getName()));
            STATIC_CLASSES.put(org.apache.commons.lang.StringUtils.class.getSimpleName(),(TemplateHashModel) staticModel.get(org.apache.commons.lang.StringUtils.class.getName()));
        } catch (TemplateModelException e) {
            e.printStackTrace();
        }
    }

    @Around("logPointCut()")
    public Object before(ProceedingJoinPoint point) throws Throwable {
        Object returnVal=null;
        try{
            String methodName = point.getSignature().getName();
            if (StringUtils.isEmpty(methodName)){
                return point.proceed();
            }
            //类
            Class<?> targetClass = point.getTarget().getClass();
            //类Action
            Action classAction = targetClass.getAnnotation(Action.class);

            //方法
            Method[] methods = targetClass.getMethods();
            Method method = null;
            for (int i = 0; i < methods.length; i++){
                if (methods[i].getName().equals(methodName)){
                    method = methods[i];
                    break;
                }
            }
            //如果横切点不是方法，返回
            if (method == null){
                return point.proceed();
            }

            //方法Action
            Action annotation = method.getAnnotation(Action.class);
            //如果方法上没有注解@Action，返回
            if(annotation==null){
                return point.proceed();
            }

            //Action描述
            String methodDescp = annotation.description();
            //增加归属模块
            SysAuditModelType modelType = annotation.ownermodel();
            //日志类型
            String execType= annotation.exectype();
            //方法上没有日志类型就从类上拿
            if(modelType==SysAuditModelType.NULL){
                if(classAction!=null){
                    modelType = classAction.ownermodel();
                }
            }
            String ownerModel= modelType.toString();
            //20191018新增逻辑
            //如果日志类型为空 则获取自定义日志类型customModel字段
            if (SysAuditModelType.NULL.toString().equals(ownerModel)){
                ownerModel = annotation.customModel();
                if(SysAuditModelType.NULL.toString().equals(ownerModel)){
                    if(classAction!=null){
                        ownerModel = classAction.customModel();
                    }
                }
            }

            //日志开关
            SysLogSwitch sysLogSwitch = sysLogSwitchService.getByModel(ownerModel);
            if(sysLogSwitch==null){
                return point.proceed();
            }
            short status =sysLogSwitch.getStatus()==null?SysLogSwitch.STATUS_CLOSE:sysLogSwitch.getStatus();
            if(status!=SysLogSwitch.STATUS_OPEN){
                return point.proceed();
            }
            //获取request对象，为空直接返回
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            if(request == null) {
                return point.proceed();
            }
            //判断是否需要异步
            boolean async = false;
            Exception exception = null;
            if(ActionExecOrder.BEFORE.equals(annotation.execOrder())){
                returnVal = "before";
            }else{
                try{
                    returnVal = point.proceed();
                }catch (Exception e){
                    SysAuditThreadLocalHolder.putParamerter("success", false);
                    exception = e;
                    logger.error(e);
                }
                async = true;
            }
            // 取到当前的操作用户
            SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
            SysAudit sysAudit = new SysAudit();
            sysAudit.setRoleid("0");
            sysAudit.setAuditid(UUIDUtils.getUUIDFor32());
            sysAudit.setOpname(methodDescp);
            //增加归属模块和日志类型
            sysAudit.setOwnermodel(ownerModel);
            sysAudit.setExectype(execType);
            sysAudit.setExetime(new Date());
            sysAudit.setExemethod(targetClass.getName() + "." + method.getName());
            String ip = RequestUtil.getIpAddr(request);
            ip = "0:0:0:0:0:0:0:1".equals(ip)?"127.0.0.1":ip;
            sysAudit.setFromip(ip);
            sysAudit.setRequesturi(request.getRequestURI());
            String agent=request.getHeader("User-Agent");
            //获取浏览器对象
            Browser browser = RequestUtil.getBrowser(request);
            //获取操作系统对象
            OperatingSystem operatingSystem = RequestUtil.getOperatingSystem(request);
            sysAudit.setOsversion(operatingSystem.getName());
            sysAudit.setBrowserinfo(browser.getName()+ StringPool.SLASH+browser.getVersion(agent));
            sysAudit.setUseragent(agent);
            sysAudit.setReqparams(RequestUtil.getParameterValueMap(request,false,false).toString());
            if(curUser != null && StringUtil.isNotEmpty(curUser.getUserId()) && !SystemConst.SUPER_ADMIN_ID.equals(curUser.getUserId())){
                if(ContextUtil.getCurrentOrg().getOrgPathname().indexOf("/"+topOrgName) != -1){
                    sysAudit.setExeOrgName(sysOrgService.getOrgByUserId(curUser.getUserId()).getOrgPathname().split("/"+topOrgName)[1]);
                }else{
                    sysAudit.setExeOrgName(sysOrgService.getOrgByUserId(curUser.getUserId()).getOrgName());
                }
            }
            if(BeanUtils.isNotEmpty(curUser)){
                String currUserId = curUser.getUserId();
                sysAudit.setExecutorid(currUserId);
                sysAudit.setExecutor(curUser.getFullName());
                sysAudit.setOrgid(ContextUtil.getCurrentOrgId());
                sysAudit.setTenantId(curUser.getTenantId());
                //是否是三员用户，如果是增加角色id
                boolean isContainThreeRole = sysRoleService.isContainThreeRole(currUserId);
                if(isContainThreeRole){
                    List<SysRole> sysRoleList = sysRoleService.queryByUserId(currUserId);
                    List<SysRole> xtgly = sysRoleList.stream().filter(a -> a.getRoleType().equals(SysRole.DEFAULT_XTGLY)).collect(Collectors.toList());
                    List<SysRole> aqbmgly = sysRoleList.stream().filter(a -> a.getRoleType().equals(SysRole.DEFAULT_AQBMGLY)).collect(Collectors.toList());
                    List<SysRole> aqsjy = sysRoleList.stream().filter(a -> a.getRoleType().equals(SysRole.DEFAULT_AQSJY)).collect(Collectors.toList());
                    if(!xtgly.isEmpty()){
                        sysAudit.setRoleid(xtgly.get(0).getRoleId());
                    }else if(!aqbmgly.isEmpty()){
                        sysAudit.setRoleid(aqbmgly.get(0).getRoleId());
                    }else if(!aqsjy.isEmpty()){
                        sysAudit.setRoleid(aqsjy.get(0).getRoleId());
                    }
                }
            }

            //添加明细信息
            String detail = SysAuditThreadLocalHolder.getDetail();
            if(async){
                LogHolder logHolder = new LogHolder();
                if(StringUtil.isEmpty(detail)){
                    detail = annotation.detail();
                    if(StringUtil.isNotEmpty(detail)){
                        logHolder.setParseDataModel(constructMapParameter(request));
                        logHolder.setNeedParse(true);
                    }
                }
                sysAudit.setDetail(detail);
                logHolder.setSysAudit(sysAudit);
                doLogAsync(logHolder);
            }else{
                if(StringUtil.isEmpty(detail)){
                    detail = annotation.detail();
                    if(StringUtil.isNotEmpty(detail)){
                        try{
                            detail = parseDetail(detail,request);
                        }catch (Exception ex) {
                            logger.error(ex.getMessage());
                            ex.printStackTrace();
                            detail=null;
                        }
                    }
                }
                sysAudit.setDetail(detail);
                sysAuditService.add(sysAudit);
            }
            if(null != exception){
                throw new Exception(exception);
            }
        } finally{
            SysAuditThreadLocalHolder.clearDetail();
            SysAuditThreadLocalHolder.clearParameters();
            SysAuditThreadLocalHolder.clearResult();
            SysAuditThreadLocalHolder.clearShouldLog();
        }
        return returnVal != null && "before".equals(returnVal) ? point.proceed() : returnVal;
    }

    //执行异步
    private void doLogAsync(LogHolder holder){
        LogExecutor logExecutor = new LogExecutor();
        logExecutor.setLogHolders(holder);
        wq.execute(logExecutor);
    }

    /**
     * //同步解析
     * @param detail 模板
     * @param request HttpServletRequest
     * @return  解析后信息
     * @throws TemplateException  TemplateException
     * @throws IOException IOException
     */
    private String parseDetail(String detail,HttpServletRequest request) throws TemplateException, IOException {
        return freemarkEngine.parseByStringTemplate(constructMapParameter(request) , detail);
    }

    /**
     * 构造最终的map参数
     * @param request  HttpServletRequest
     * @return map
     */
    private Map<String, Object> constructMapParameter(HttpServletRequest request){

        Map<String, Object> map=new HashMap<>();
        // 添加用户名
        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        if(BeanUtils.isNotEmpty(curUser)){
             map.put("currentUserEntity", curUser);
        }

        //添加Request查询参数
        map.putAll(RequestUtil.getQueryMap(request));

        map.put("request", request);
        //添加线程相关变量
        map.putAll(SysAuditThreadLocalHolder.getParamerters());
        //添加通用的服务类
        //map.put("SysAuditLinkService", SysAuditLinkService);
        initCommonServices();
        map.putAll(commonServices);
        //添加通用静态类
        map.putAll(STATIC_CLASSES);

        return map;
    }

    /**
     * 将所有继承自{@link BaseService}的类添加到commonServices中。
     */
    private void initCommonServices(){
        if(isCommonServicesInited){
            return;
        }
        String[] beanNames = AppUtil.getContext().getBeanDefinitionNames();
        for(String beanName:beanNames){
            Object bean = AppUtil.getBean(beanName);
            if(BeanUtils.isInherit(bean.getClass(), BaseService.class)){
                commonServices.put(beanName, bean);
            }
        }
        isCommonServicesInited = true;
    }
}


/**
 * 作业队列
 */
class WorkQueue{
    private final int nThreads;
    private final PoolWorker[] threads;
    LinkedList<Runnable> queue;

    public WorkQueue(int nThreads){
        this.nThreads=nThreads;
        queue = new LinkedList<Runnable>();
        threads = new PoolWorker[nThreads];
        for(int i=0;i<this.nThreads;i++){
            threads[i] = new PoolWorker();
            threads[i].start();
        }
    }

    public void execute(Runnable r){
        synchronized (queue) {
            queue.addLast(r);
            queue.notify();
        }
    }

    private class PoolWorker extends Thread{
        private Log logger = LogFactory.getLog(PoolWorker.class);
        @Override
        public void run(){
            Runnable r;
            while(true){
                synchronized (queue) {
                    while(queue.isEmpty()){
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            logger.error(e.getMessage());
                        }
                    }
                    r=queue.removeFirst();
                }
                try{
                    r.run();
                }catch (Exception e) {
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }
        }
    }
}


/**
 * 执行记录日志的任务作业
 */
class LogExecutor implements Runnable{
    private Log logger = LogFactory.getLog(LogExecutor.class);
    private LogHolder logHolder;
    private FreemarkEngine freemarkEngine;
    private SysAuditService sysAuditService;

    public void setLogHolders(LogHolder logHolder) {
        this.logHolder = logHolder;
        this.sysAuditService =  AppUtil.getBean(SysAuditService.class);
        this.freemarkEngine = AppUtil.getBean(FreemarkEngine.class);
    }

    private void doLog() throws TemplateException, IOException{
        SysAudit sysAudit = logHolder.getSysAudit();
            if(logHolder.isNeedParse()){
            String detail = freemarkEngine.parseByStringTemplate(logHolder.getParseDataModel(),sysAudit.getDetail());
            sysAudit.setDetail(detail);
            String opName = freemarkEngine.parseByStringTemplate(logHolder.getParseDataModel(),sysAudit.getOpname());
            sysAudit.setOpname(opName);
        }
        sysAuditService.add(sysAudit);
    }

    @Override
    public void run() {
        try {
            doLog();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        }
    }
}

/**
 * 日志信息容器
 */
class  LogHolder{
    SysAudit sysAudit;
    boolean needParse=false;
    Map<String, Object> parseDataModel;

    public SysAudit getSysAudit() {
        return sysAudit;
    }
    public void setSysAudit(SysAudit sysAudit) {
        this.sysAudit = sysAudit;
    }
    public boolean isNeedParse() {
        return needParse;
    }
    public void setNeedParse(boolean needParse) {
        this.needParse = needParse;
    }
    public Map<String, Object> getParseDataModel() {
        return parseDataModel;
    }
    public void setParseDataModel(Map<String, Object> parseDataModel) {
        this.parseDataModel = parseDataModel;
    }
}